/* This software was developed at the National Institute of Standards and
 * Technology by employees of the Federal Government in the course of
 * their official duties. Pursuant to title 17 Section 105 of the United
 * States Code this software is not subject to copyright protection and
 * is in the public domain.
 * NIST assumes no responsibility whatsoever for its use by other parties,
 * and makes no guarantees, expressed or implied, about its quality,
 * reliability, or any other characteristic.
 * <BR>
 * We would appreciate acknowledgement if the software is used.
 * <BR>
 * NIST ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
 * DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING
 * FROM THE USE OF THIS SOFTWARE.
 * </PRE></P>
 * @author  rouil
 */

#include "serviceflowhandler.h"
#include "mac802_16.h"
#include "scheduling/wimaxscheduler.h"
#include "scheduling/bsscheduler.h"

static int TransactionID = 0;

/* 
 * Create a service flow
 * @param mac The Mac where it is located
 */
ServiceFlowHandler::ServiceFlowHandler() {
    LIST_INIT(&flow_head_);
    LIST_INIT(&pendingflow_head_);
}

/*
 * Set the mac it is located in
 * @param mac The mac it is located in
 */
void ServiceFlowHandler::setMac(Mac802_16 *mac) {
    assert(mac);

    mac_ = mac;
}

/**
 * Process the given packet. Only service related packets must be sent here.
 * @param p The packet received
 */
void ServiceFlowHandler::process(Packet * p) {
    hdr_mac802_16 *wimaxHdr = HDR_MAC802_16(p);
    gen_mac_header_t header = wimaxHdr->header;

    //we cast to this frame because all management frame start with
    //a type 
    mac802_16_dl_map_frame *frame = (mac802_16_dl_map_frame*) p->accessdata();

    switch (frame->type) {
        case MAC_DSA_REQ:
            processDSA_req(p);
            break;
        case MAC_DSA_RSP:
            processDSA_rsp(p);
            break;
        case MAC_DSA_ACK:
            processDSA_ack(p);
            break;
        default:
            printf("Unknow frame type (%d) in flow handler\n", frame->type);
    }
    //Packet::free (p);
}

/**
 * Add a flow with the given qos
 * @param qos The QoS for the flow
 * @return the created ServiceFlow
 */
ServiceFlow* ServiceFlowHandler::addFlow(ServiceFlowQoS * qos) {
    return NULL;
}

/**
 * Remove the flow given its id
 * @param id The flow id
 */
void ServiceFlowHandler::removeFlow(int id) {

}

/**
 * Checking required bandwidth (uplink)
 * @param ServiceFlow and Serviceflow qos setting of uplink, peernode pointer
 */
void ServiceFlowHandler::checking_req_bw(ServiceFlow& service_flow, PeerNode *peer) {
    //service_flow.printInformation();
    OFDMPhy *phy = mac_->getPhy();
    FrameMap *map = mac_->getMap();
    BSScheduler *bsscheduler = (BSScheduler *) mac_->getScheduler();
    SchedulingType_t type = service_flow.getScheduling();
    int & burstNbForQos = bsscheduler->qos_record_for_ul[type].burst_mount;
    int & reserveBurstNb = bsscheduler->qos_record_for_ul[SERVICE_BE].burst_mount;
    double frame_duration = mac_->macmib_.frame_duration;
    double txtime;
    int txtime_s;
    double request_burst;

    assert(type!=SERVICE_undefine);
    switch(type) {
        case SERVICE_UGS:
            txtime = phy->getTrxSymbolTime(service_flow.getQoS()->getBurstSize(), \
                    map->getUlSubframe()->getProfile(peer->getDIUC() - DIUC_PROFILE_1 + UIUC_PROFILE_1)->getEncoding());
            txtime_s = (int) round(txtime / phy->getSymbolTime());
            request_burst = frame_duration / service_flow.getQoS()->getInterval() * txtime_s;
            if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos) {
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos + reserveBurstNb) {
                reserveBurstNb -= (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                burstNbForQos += (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else {
                burstNbForQos += reserveBurstNb;
                reserveBurstNb = 0;
                bsscheduler->qos_record_for_ul[type].used += request_burst;
                printf("\033[0;32;31mAvailable burst for qos UGS is almost full!\033[m\n");
            }
            break;
        case SERVICE_ertPS:                                             /*try to grant minimum bw*1.2*/
            txtime = phy->getTrxSymbolTime((int)(service_flow.getQoS()->getMinimumBandwidth()*1.2f) , \
                    map->getUlSubframe()->getProfile(peer->getDIUC() - DIUC_PROFILE_1 + UIUC_PROFILE_1)->getEncoding());
            txtime_s = (int) round(txtime / phy->getSymbolTime());
            request_burst = frame_duration / service_flow.getQoS()->getInterval() * txtime_s;
            if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos) {
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos + reserveBurstNb) {
                reserveBurstNb -= (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                burstNbForQos += (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else if(round(request_burst/1.2f) + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos + reserveBurstNb) {
                reserveBurstNb -= (int)ceil(round(request_burst/1.2f) + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                burstNbForQos += (int)ceil(round(request_burst/1.2f) + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                bsscheduler->qos_record_for_ul[type].used += round(request_burst/1.2f);
            } else {
                burstNbForQos += reserveBurstNb;
                reserveBurstNb = 0;
                bsscheduler->qos_record_for_ul[type].used += request_burst;
                printf("\033[0;32;31mAvailable burst for qos ertPS is almost full!\033[m\n");
            }
            break;
        case SERVICE_rtPS:
            txtime = phy->getTrxSymbolTime((int)(service_flow.getQoS()->getMinimumBandwidth()) , \
                    map->getUlSubframe()->getProfile(peer->getDIUC() - DIUC_PROFILE_1 + UIUC_PROFILE_1)->getEncoding());
            txtime_s = (int) ceil(txtime / phy->getSymbolTime());
            //request_burst =  1.0f/(double)(service_flow.getQoS()->getRequestInterval()) + frame_duration / service_flow.getQoS()->getInterval() * (double)txtime_s;
            request_burst =  (1.0f + (double)txtime_s )/(double)(service_flow.getQoS()->getRequestInterval());
            if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos) {
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos + reserveBurstNb) {
                reserveBurstNb -= (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                burstNbForQos += (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else {
                burstNbForQos += reserveBurstNb;
                reserveBurstNb = 0;
                bsscheduler->qos_record_for_ul[type].used += request_burst;
                printf("\033[0;32;31mAvailable burst for qos rtPS is almost full!\033[m\n");
            }
            break;
        case SERVICE_nrtPS:
            txtime = phy->getTrxSymbolTime((int)(service_flow.getQoS()->getMinimumBandwidth()) , \
                    map->getUlSubframe()->getProfile(peer->getDIUC() - DIUC_PROFILE_1 + UIUC_PROFILE_1)->getEncoding());
            txtime_s = (int) round(txtime / phy->getSymbolTime());
            request_burst =  1.0f/(double)(service_flow.getQoS()->getRequestInterval()) + frame_duration / service_flow.getQoS()->getInterval() * (double)txtime_s;
            if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos) {
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else if(request_burst + bsscheduler->qos_record_for_ul[type].used <= burstNbForQos + reserveBurstNb) {
                reserveBurstNb -= (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                burstNbForQos += (int)ceil(request_burst + bsscheduler->qos_record_for_ul[type].used - burstNbForQos);
                bsscheduler->qos_record_for_ul[type].used += request_burst;
            } else {
                burstNbForQos += reserveBurstNb;
                reserveBurstNb = 0;
                bsscheduler->qos_record_for_ul[type].used += request_burst;
                printf("\033[0;32;31mAvailable burst for qos nrtPS is almost full!\033[m\n");
            }
            break;
        case SERVICE_BE:
            if(reserveBurstNb <= 0) {
                printf("\033[0;32;31mAvailable burst for qos BE is almost full!\033[m\n");
            }
            break;
        default:
            break;
    }
    bsscheduler->printqos();
    return;
}

/*
 *Send a data flow request to the given node
 * * (Called by SS only)
 * 1. create DSA_REQ
 * 2. fill up the serviceflow parameter of DSA_REQ
 */
void ServiceFlowHandler::sendDataflowRequest(int node_index, int flow_index, int up) {
    Packet *p;
    struct hdr_cmn *ch;
    hdr_mac802_16 *wimaxHdr;
    mac802_16_dsa_req_frame *dsa_frame;
    PeerNode *peer;

    //create packet for request
    peer = mac_->getPeerNode(node_index); //called by SS, so the peer must be head_of_list
    p = mac_->getPacket();
    ch = HDR_CMN(p);
    wimaxHdr = HDR_MAC802_16(p);
    p->allocdata(sizeof (struct mac802_16_dsa_req_frame));
    dsa_frame = (mac802_16_dsa_req_frame*) p->accessdata();
    dsa_frame->type = MAC_DSA_REQ;
    dsa_frame->uplink = (up && mac_->getNodeType() == STA_MN) || (!up && mac_->getNodeType() == STA_BS);
    dsa_frame->transaction_id = TransactionID++;

    if (mac_->getNodeType() == STA_MN) {
        ch->size() += GET_DSA_REQ_SIZE(0);
        if (up) {
            mac_->UpflowTable[flow_index].transactionID = dsa_frame->transaction_id; // store the unique ID
            dsa_frame->serviceFlow = new ServiceFlow(*mac_->UpflowTable[flow_index].qosRecord);
        } else {
            mac_->DownflowTable[flow_index].transactionID = dsa_frame->transaction_id; // store the unique ID
            dsa_frame->serviceFlow = new ServiceFlow(*mac_->DownflowTable[flow_index].qosRecord);
        }
    } else {
        fprintf(stderr, "\n\tBS should not send dataflow request(not implement)\n\n");
        assert(0);
        //assign a CID and include it in the message
        //Connection *data = new Connection(CONN_DATA);
        //mac_->getCManager()->add_connection(data, up);
        //if (up)
        //    peer->addOutData(data);
        //else
        //    peer->addInData(data);
        //dsa_frame->cid = data->get_cid();
        //ch->size() += GET_DSA_REQ_SIZE(1);
    }

    wimaxHdr->header.cid = peer->getPrimary(OUT_CONNECTION)->get_cid();
    peer->getPrimary(OUT_CONNECTION)->enqueue(p);
}

/**
 * Send a flow request to the given node
 * @param index The node address
 * @param out The flow direction
 * (Called by SS only)
 * 1. create DSA_REQ
 * 2. fill up the serviceflow parameter of DSA_REQ
 */
/*
void ServiceFlowHandler::sendFlowRequest(int index, bool out) {
    Packet *p;
    struct hdr_cmn *ch;
    hdr_mac802_16 *wimaxHdr;
    mac802_16_dsa_req_frame *dsa_frame;
    PeerNode *peer;

    //create packet for request
    peer = mac_->getPeerNode(index);
    p = mac_->getPacket();
    ch = HDR_CMN(p);
    wimaxHdr = HDR_MAC802_16(p);
    p->allocdata(sizeof (struct mac802_16_dsa_req_frame));
    dsa_frame = (mac802_16_dsa_req_frame*) p->accessdata();
    dsa_frame->type = MAC_DSA_REQ;
    dsa_frame->uplink = (out && mac_->getNodeType() == STA_MN) || (!out && mac_->getNodeType() == STA_BS);
    //actually only STA_MN need to send FlowRequest, STA_BS not implement
    dsa_frame->transaction_id = TransactionID++;
    //dsa_frame->Qos = SERVICE_undefine;
    //dsa_frame->QosParameter = NULL;
    if (mac_->getNodeType() == STA_MN) {
        ch->size() += GET_DSA_REQ_SIZE(0);
    } else { //DSA_REQ won't be send by BS, following things won't touch.
        //assign a CID and include it in the message
        Connection *data = new Connection(CONN_DATA);
        mac_->getCManager()->add_connection(data, out);
        if (out)
            peer->setOutData(data);
        else
            peer->setInData(data);
        dsa_frame->cid = data->get_cid();
        ch->size() += GET_DSA_REQ_SIZE(1);
    }

    wimaxHdr->header.cid = peer->getPrimary(OUT_CONNECTION)->get_cid();
    peer->getPrimary(OUT_CONNECTION)->enqueue(p);
}
 */

/**
 * process a flow request
 * @param p The received request
 * (AT BS NODE)
 * 3. create a DATA_CONNECTION
 * 4. fill up the serviceflow parameteer of the connection
 * 5. fill up the CID of the connection
 * 6. create DSA_RSP
 * 7. fill up the serviceflow parameter of DSA_RSP
 * 8. send DSA_RSP
 */
void ServiceFlowHandler::processDSA_req(Packet *p) {
    mac_->debug("At %f in Mac %d received DSA request from %d\n", NOW, mac_->addr(), HDR_MAC802_16(p)->header.cid);

    Packet *rsp;
    struct hdr_cmn *ch;
    hdr_mac802_16 *wimaxHdr_req;
    hdr_mac802_16 *wimaxHdr_rsp;
    mac802_16_dsa_req_frame *dsa_req_frame;
    mac802_16_dsa_rsp_frame *dsa_rsp_frame;
    PeerNode *peer;
    Connection *data;

    //read the request
    wimaxHdr_req = HDR_MAC802_16(p);
    dsa_req_frame = (mac802_16_dsa_req_frame*) p->accessdata();
    peer = mac_->getCManager()->get_connection(wimaxHdr_req->header.cid, true)->getPeerNode();

    //allocate response
    //create packet for request
    rsp = mac_->getPacket();
    ch = HDR_CMN(rsp);
    wimaxHdr_rsp = HDR_MAC802_16(rsp);
    rsp->allocdata(sizeof (struct mac802_16_dsa_rsp_frame));
    dsa_rsp_frame = (mac802_16_dsa_rsp_frame*) rsp->accessdata();
    dsa_rsp_frame->type = MAC_DSA_RSP;
    dsa_rsp_frame->transaction_id = dsa_req_frame->transaction_id;
    dsa_rsp_frame->uplink = dsa_req_frame->uplink;
    dsa_rsp_frame->confirmation_code = 0; //OK

    if (mac_->getNodeType() == STA_MN) {
        //the message contains the CID for the connection
        assert(0); //won't touch
        data = new Connection(CONN_DATA, dsa_req_frame->cid);
        if (dsa_req_frame->uplink) {
            mac_->getCManager()->add_connection(data, OUT_CONNECTION);
            peer->addOutData(data);
        } else {
            mac_->getCManager()->add_connection(data, IN_CONNECTION);
            peer->addInData(data);
        }
        ch->size() += GET_DSA_RSP_SIZE(0);
    } else { // only BS will receive DSA_REQ
        data = new Connection(CONN_DATA);
        //data->get_serviceflow()->printInformation();
        //confirm the required Qos, then send a copy to STA_MN
        if (dsa_req_frame->uplink) {
            //@todo: check the required bandwidth
            data->set_serviceflow(dsa_req_frame->serviceFlow);
            checking_req_bw(*data->get_serviceflow(),peer);
            
            mac_->getCManager()->add_connection(data, IN_CONNECTION);
            peer->addInData(data);
        } else {
            data->set_serviceflow(dsa_req_frame->serviceFlow);
            mac_->getCManager()->add_connection(data, OUT_CONNECTION);
            peer->addOutData(data);
        }
        dsa_rsp_frame->serviceFlow = new ServiceFlow(*data->get_serviceflow());
        dsa_rsp_frame->cid = data->get_cid();
        ch->size() += GET_DSA_RSP_SIZE(1);
    }

    wimaxHdr_rsp->header.cid = peer->getPrimary(OUT_CONNECTION)->get_cid();
    peer->getPrimary(OUT_CONNECTION)->enqueue(rsp);
}

/**
 * process a flow response
 * @param p The received response
 * 9. create a data connection
 *10.fill up the serviceflow parameter of the connection
 *11.fill up the CID of the connection
 *12.create and send DSA_ACK
 */
void ServiceFlowHandler::processDSA_rsp(Packet *p) {
    mac_->debug("At %f in Mac %d received DSA response\n", NOW, mac_->addr());

    Packet *ack;
    struct hdr_cmn *ch;
    hdr_mac802_16 *wimaxHdr_ack;
    hdr_mac802_16 *wimaxHdr_rsp;
    mac802_16_dsa_ack_frame *dsa_ack_frame;
    mac802_16_dsa_rsp_frame *dsa_rsp_frame;
    Connection *data;
    PeerNode *peer;

    //read the request
    wimaxHdr_rsp = HDR_MAC802_16(p);
    dsa_rsp_frame = (mac802_16_dsa_rsp_frame*) p->accessdata();
    peer = mac_->getCManager()->get_connection(wimaxHdr_rsp->header.cid, true)->getPeerNode();

    //TBD: check if status not OK

    if (mac_->getNodeType() == STA_MN) {
        //the message contains the CID for the connection
        data = new Connection(CONN_DATA, dsa_rsp_frame->cid);
        data->set_serviceflow(dsa_rsp_frame->serviceFlow);
        if (dsa_rsp_frame->uplink) {
            mac_->getCManager()->add_connection(data, OUT_CONNECTION);
            peer->addOutData(data);
            for (vector<flow_record>::iterator it = mac_->UpflowTable.begin(); it != mac_->UpflowTable.end(); it++) {
                if (it->transactionID == dsa_rsp_frame->transaction_id) {
                    it->cid = dsa_rsp_frame->cid;
                    break;
                }
            }
        } else {
            mac_->getCManager()->add_connection(data, IN_CONNECTION);
            peer->addInData(data);
            for (vector<flow_record>::iterator it = mac_->DownflowTable.begin(); it != mac_->DownflowTable.end(); it++) {
                if (it->transactionID == dsa_rsp_frame->transaction_id) {
                    it->cid = dsa_rsp_frame->cid;
                    break;
                }
                assert(0); //won't touch
            }
        }
    }

    //allocate ack
    //create packet for request
    ack = mac_->getPacket();
    ch = HDR_CMN(ack);
    wimaxHdr_ack = HDR_MAC802_16(ack);
    ack->allocdata(sizeof (struct mac802_16_dsa_ack_frame));
    dsa_ack_frame = (mac802_16_dsa_ack_frame*) ack->accessdata();
    dsa_ack_frame->type = MAC_DSA_ACK;
    dsa_ack_frame->transaction_id = dsa_rsp_frame->transaction_id;
    dsa_ack_frame->uplink = dsa_rsp_frame->uplink;
    dsa_ack_frame->confirmation_code = 0; //OK
    ch->size() += DSA_ACK_SIZE;

    wimaxHdr_ack->header.cid = peer->getPrimary(OUT_CONNECTION)->get_cid();
    peer->getPrimary(OUT_CONNECTION)->enqueue(ack);
}

/**
 * process a flow request
 * @param p The received response
 */
void ServiceFlowHandler::processDSA_ack(Packet *p) {
    mac_->debug("At %f in Mac %d received DSA ack\n", NOW, mac_->addr());
}
